﻿using System;
using System.Text;
using System.Resources;
using System.Globalization;

using LeanEngine.Extensions;

namespace LeanEngine.Validation
{
    /// <summary>
    /// Defines the base interface for creating data validation attributes.
    /// </summary>
    [AttributeUsage(AttributeTargets.Property)]
    public abstract class ValidationAttribute : Attribute
    {
        #region Declaration Members

        private string _errorMessages;

        #endregion

        #region Constructor Members

        /// <summary>
        /// Initialize a new instance of LeanEngine.Validation.ValidationAttribute class with a error message obtained from a resource file.
        /// </summary>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <param name="resourceType">The System.Type of the resource manager that holds the error message.</param>
        /// <param name="errorKey">The key error message contained in the resource.</param>
        protected ValidationAttribute(Type resourceType, string errorKey)
        {
            if (resourceType.IsNull()) throw new ArgumentNullException("resourceType");
            if (errorKey.IsNullOrEmpty()) throw new ArgumentNullException("errorKey");

#if TRIAL_BUILD
            DateTime.Now.Evaluate();
#endif
            
            this._errorMessages = new ResourceManager(resourceType).GetString(errorKey);
        }

        /// <summary>
        /// Initialize a new instance of LeanEngine.Validation.ValidationAttribute class with an specific error message.
        /// </summary>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <param name="errorMessage">The error message displayed for non valid values.</param>
        protected ValidationAttribute(string errorMessage)
        {
            if (errorMessage.IsNullOrEmpty()) throw new ArgumentNullException("errorMessage");
            this._errorMessages = errorMessage;
        }

        #endregion

        #region PropertyInfo Members

        /// <summary>
        /// Gets or sets the error message to be displayed in case of validation failure.
        /// </summary>
        public virtual string ErrorMessage
        {
            get { return _errorMessages; }
            set { _errorMessages = value; }
        }

        #endregion

        #region Method Members

        /// <summary>
        /// Validates the value according to the validation rule and throws an exception in case of validation failure.
        /// </summary>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.ArgumentException"></exception>
        /// <param name="value">The value to be validated.</param>
        public abstract bool Validate(object value);

        /// <summary>
        /// Validates the value according to the validation rule and add the error message to the container in case of validation failure.
        /// </summary>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.ArgumentException"></exception>
        /// <param name="value">The value to be validated.</param>
        /// <param name="errorMessageContainer">The container for all the error messages.</param>
        public abstract void Validate(object value, StringBuilder errorMessageContainer);

        internal static object ConvertArgument(string argument, ArgumentConvertType convertType)
        {
            if (string.IsNullOrEmpty(argument)) throw new ArgumentNullException("argument");

            switch (convertType)
            {
                case ArgumentConvertType.DateTime:
                    try
                    {
                        return DateTime.ParseExact(argument, "s", CultureInfo.InvariantCulture);
                    }
                    catch (FormatException ex)
                    {
                        throw new ArgumentException("'" + argument + "' is not a valid ISO8601 date. It must be like '2006-01-20T00:00:00'.", ex);
                    }

                default: //case ArgumentConvertType.Decimal:
                    decimal tmp = 0;
                    if (!decimal.TryParse(argument, NumberStyles.Any, CultureInfo.GetCultureInfo("en-US").NumberFormat, out tmp))
                    {
                        throw new ArgumentException("'" + argument + "' is not a numeric value.");
                    }

                    return tmp;
            }
        }

        #endregion
    }
}
